package com.zy.explore.sys.application.service;

import com.alibaba.fastjson.JSON;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.zy.explore.common.application.service.RedisService;
import com.zy.explore.common.infrastructure.util.ChineseCalendar;
import com.zy.explore.common.infrastructure.util.LambdaUtil;
import com.zy.explore.common.infrastructure.util.MyDateUtil;
import com.zy.explore.sys.domain.mybatis.dao.DeptUserDao;
import com.zy.explore.sys.domain.mybatis.dao.SysDeptDao;
import com.zy.explore.sys.domain.mybatis.dao.SysUserDao;
import com.zy.explore.sys.domain.mybatis.entity.DeptUser;
import com.zy.explore.sys.domain.mybatis.entity.SysDept;
import com.zy.explore.sys.domain.mybatis.entity.SysUser;
import com.zy.explore.sys.infrastructure.cache.DingTokenCache;
import com.zy.explore.sys.infrastructure.constant.DingTalkResource;
import com.zy.explore.sys.infrastructure.constant.SysModuleConstant;
import com.zy.explore.sys.infrastructure.third.dingtalk.feign.DingTalkFeign;
import com.zy.explore.sys.infrastructure.third.dingtalk.frm.AttendListFrm;
import com.zy.explore.sys.infrastructure.third.dingtalk.frm.DeptUserIdListFrm;
import com.zy.explore.sys.infrastructure.third.dingtalk.frm.GetDeptDetailFrm;
import com.zy.explore.sys.infrastructure.third.dingtalk.frm.GetDeptUserFrm;
import com.zy.explore.sys.infrastructure.third.dingtalk.frm.GetDingDeptListFrm;
import com.zy.explore.sys.infrastructure.third.dingtalk.frm.GetSubDeptIdFrm;
import com.zy.explore.sys.infrastructure.third.dingtalk.frm.GetTokenFrm;
import com.zy.explore.sys.infrastructure.third.dingtalk.frm.GetUserDetailFrm;
import com.zy.explore.sys.infrastructure.third.dingtalk.frm.TokenFrm;
import com.zy.explore.sys.infrastructure.third.dingtalk.vo.DingAttendListVO;
import com.zy.explore.sys.infrastructure.third.dingtalk.vo.DingDeptDetailVO;
import com.zy.explore.sys.infrastructure.third.dingtalk.vo.DingDeptListVO;
import com.zy.explore.sys.infrastructure.third.dingtalk.vo.DingDeptVO;
import com.zy.explore.sys.infrastructure.third.dingtalk.vo.DingRecordVO;
import com.zy.explore.sys.infrastructure.third.dingtalk.vo.DingTokenResultVO;
import com.zy.explore.sys.infrastructure.third.dingtalk.vo.DingUserDetailVO;
import com.zy.explore.sys.infrastructure.third.dingtalk.vo.DingUserListVO;
import com.zy.explore.sys.infrastructure.third.dingtalk.vo.DingUserVO;
import com.zy.explore.sys.infrastructure.third.dingtalk.vo.ListUserByDeptIdResultVO;
import com.zy.explore.sys.infrastructure.third.dingtalk.vo.SubDeptIdListVO;
import com.zy.explore.sys.interfaces.vo.DeptTreeVO;
import lombok.extern.slf4j.Slf4j;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;


/**
 * description 钉钉接口服务实现类
 *
 * @author ChinaYin  主页:https://gitee.com/zy-explorej2ee 邮箱:zyexplorej2ee@sina.com
 * @date 2021年04月03日 20:50
 **/
@Slf4j
@Service
public class DingTalkService {

    @Value("${dingTalk.appKey:}")
    private String dingTalkAppKey;

    @Value("${dingTalk.appSecret:}")
    private String dingTalkAppSecret;

    @Resource
    private SysDeptDao sysDeptDao;

    @Resource
    private DeptUserDao deptUserDao;

    @Resource
    private SysUserDao sysUserDao;

    @Resource
    private DingTalkFeign dingTalkFeign;

    @Resource
    private Redisson redisson;

    @Resource
    private RedisService redisService;

    /**
     * 获取钉钉token
     *
     * @return 返回钉钉接口token
     */
    public String getDingToken() {
        // token先从缓存中获取，如果获取不到，则调钉钉接口获取
        String s = DingTalkResource.tokenMap.get(DingTalkResource.TOKEN_KEY);
        if (StringUtils.isEmpty(s)) {
            return getToken();
        } else {
            DingTokenCache dingTokenCache = JSON.parseObject(s, DingTokenCache.class);
            if (dingTokenCache.getExpiresAt().getTime() <= System.currentTimeMillis()) {
                return getToken();
            }
            String accessToken = dingTokenCache.getAccessToken();
            return accessToken;
        }
    }

    private String getToken() {
        try {
            DingTokenResultVO result = dingTalkFeign.getToken(GetTokenFrm.builder().appkey(dingTalkAppKey).appsecret(dingTalkAppSecret).build());
            if (Objects.equals(result.getErrcode(), DingTalkResource.DD_OK)) {
                String accessToken = result.getAccess_token();
                // 将从钉钉接口获取的token存到本地缓存中
                DingTalkResource.tokenMap.put(DingTalkResource.TOKEN_KEY, JSON.toJSONString(DingTokenCache.builder().accessToken(accessToken).expiresAt(new Date(System.currentTimeMillis() + 7000 * 1000)).build()));
                log.info("从钉钉获取的token:{}", accessToken);
                return accessToken;
            } else {
                log.info("调用钉钉获取token接口，返回错误，提示信息：{}", result.getErrmsg());
            }
        } catch (Exception e) {
            log.error("调用钉钉获取token接口异常：", e);
        }
        return null;
    }

    /**
     * 获取钉钉部门集合
     *
     * @return 返回钉钉所有部门集合
     */
    public List<DingDeptVO> getDingDeptList() {
        List<DingDeptVO> retList = Lists.newArrayList();
        try {
            DingDeptListVO dingTalkDeptListResult = dingTalkFeign.getDingTalkDeptList(GetDingDeptListFrm.builder().access_token(getDingToken()).fetch_child(true).build());
            if (Objects.equals(dingTalkDeptListResult.getErrcode(), DingTalkResource.DD_OK)) {
                retList = dingTalkDeptListResult.getDepartment();
            } else {
                log.info("调用钉钉获取所有部门的接口返回错误，提示信息：{}", dingTalkDeptListResult.getErrmsg());
            }
        } catch (Exception e) {
            log.error("调用钉钉获取所有部门的接口异常：", e);
        }
        if (CollectionUtils.isEmpty(retList)) {
            retList = Lists.newArrayList();
        }
        return retList;
    }

    /**
     * 根据部门ID获取其子部门集合
     *
     * @param deptId 部门ID
     * @param flag 是否递归查询所有子部门
     * @return 返回子部门集合
     */
    public List<Long> getDingSubDeptIdList(Long deptId, Boolean flag) {
        Set<Long> deptIdSet = Sets.newHashSet();
        try {
            SubDeptIdListVO resultVO = dingTalkFeign.getDingSubDeptIdList(TokenFrm.builder().access_token(getDingToken()).build(), GetSubDeptIdFrm.builder().dept_id(deptId).build());
            if (Objects.equals(resultVO.getErrcode(), DingTalkResource.DD_OK)) {
                List<Long> result = resultVO.getResult().getDept_id_list();
                if (result == null || result.isEmpty()) {
                    return Lists.newArrayList(deptIdSet);
                }
                Set<Long> subIdSet = Sets.newHashSet(result);
                if (CollectionUtils.isEmpty(subIdSet)) {
                    return Lists.newArrayList(deptIdSet);
                }
                deptIdSet.addAll(subIdSet);
                if (flag) {
                    for (Long subId : subIdSet) {
                        deptIdSet.addAll(getDingSubDeptIdList(subId, true));
                    }
                    return Lists.newArrayList(deptIdSet);
                }
            } else {
                log.info("调用钉钉获取部门子部门ID集合的接口返回错误，提示信息：{}", resultVO.getErrmsg());
            }
        } catch (Exception e) {
            log.error("调用钉钉获取部门子部门ID集合的接口异常：", e);
        }
        return Lists.newArrayList(deptIdSet);
    }

    /**
     * 根据钉钉部门ID查询部门下面的人员
     *
     * @param deptId 部门ID
     * @param flag 是否递归查询所有子部门的人员
     * @return 返回部门下面人员集合
     */
    public List<DingUserVO> getDingUserList(Long deptId, Boolean flag) {
        List<DingUserVO> retList = Lists.newArrayList();
        try {
            DingUserListVO result = dingTalkFeign.getDeptUserList(GetDeptUserFrm.builder().access_token(getDingToken()).department_id(deptId).build());
            if (Objects.equals(result.getErrcode(), DingTalkResource.DD_OK)) {
                List<DingUserVO> userList = result.getUserlist();
                retList.addAll(userList);
                if (flag) {
                    List<Long> subIdList = this.getDingSubDeptIdList(deptId, true);
                    if (!CollectionUtils.isEmpty(subIdList)) {
                        for (Long subId : subIdList) {
                            retList.addAll(getDingUserList(subId, true));
                        }
                    }
                }
            } else {
                log.info("调用钉钉获取部门下用户的接口返回错误，提示信息：{}", result.getErrmsg());
            }
        } catch (Exception e) {
            log.error("调用钉钉获取部门下用户的接口异常：", e);
        }
        // 去重
        if (!CollectionUtils.isEmpty(retList)) {
            retList = retList.stream().filter(LambdaUtil.distinctByKey(DingUserVO::getUserid)).collect(Collectors.toList());
            //retList = retList.stream().collect(Collectors.collectingAndThen(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(DingUserVO::getUserId))), ArrayList::new));
        }
        return retList;
    }

    /**
     * 查询某个部门某年某月的加班餐补明细
     *
     * @param deptName 部门名称
     * @param deptId   部门ID
     * @param year     年
     * @param month    月
     * @return 返回该部门加班补贴明细
     */
    public Map<String, List<List<String>>> getDeptOverTimeAttendListMap(String deptName, Long deptId, Integer year, Integer month) throws Exception {
        Map<String, List<List<String>>> retMap = Maps.newHashMap();
        List<DingUserVO> dingUserList = this.getDingUserList(deptId, true);
        // 对员工去重(方式1)
//        TreeSet<DingUserDTO> dingUserSet = Sets.newTreeSet(Comparator.comparing(DingUserDTO::getUserId));
//        dingUserSet.addAll(dingUserList);
//        dingUserList = Lists.newArrayList(dingUserSet);
        // 对员工去重(方式2)
        dingUserList = dingUserList.stream().collect(Collectors.collectingAndThen(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(DingUserVO::getUserid))), ArrayList::new));

        String beginDateStr = MyDateUtil.getBeginDateStr(year, month);
        String endDateStr = MyDateUtil.getEndDateStr(year, month);
        List<String> dateList = MyDateUtil.getDateList(5, beginDateStr + "~" + endDateStr, 180);

        if (CollectionUtils.isEmpty(dingUserList)) {
            return retMap;
        }
        retMap = this.getEmpDayRecords(year, month, dateList, dingUserList);
        List<List<String>> deptTotal = calcTotal(deptName, year, month, retMap);
        Map<String, List<List<String>>> finalMap = Maps.newLinkedHashMap();
        finalMap.put(deptName + year + "年" + month + "月份-餐补总计", deptTotal);
        finalMap.putAll(retMap);
        return finalMap;
    }

    public List<List<String>> getDeptOverTimeAttendList(String deptName, Long deptId, Integer year, Integer month) throws Exception {
        List<List<String>> retList = Lists.newArrayList();
        List<DingUserVO> dingUserList = this.getDingUserList(deptId, true);
        // 对员工去重(方式1)
//        TreeSet<DingUserDTO> dingUserSet = Sets.newTreeSet(Comparator.comparing(DingUserDTO::getUserId));
//        dingUserSet.addAll(dingUserList);
//        dingUserList = Lists.newArrayList(dingUserSet);
        // 对员工去重(方式2)
        dingUserList = dingUserList.stream().collect(Collectors.collectingAndThen(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(DingUserVO::getUserid))), ArrayList::new));

        String beginDateStr = MyDateUtil.getBeginDateStr(year, month);
        String endDateStr = MyDateUtil.getEndDateStr(year, month);
        List<String> dateList = MyDateUtil.getDateList(5, beginDateStr + "~" + endDateStr, 180);

        if (CollectionUtils.isEmpty(dingUserList)) {
            return retList;
        }
        Map<String, List<List<String>>> retMap = this.getEmpDayRecords(year, month, dateList, dingUserList);
        List<List<String>> deptTotal = calcTotal(deptName, year, month, retMap);
        retList.addAll(deptTotal);
        // 加一行空行
        retList.add(Arrays.asList("", "", "", "", ""));
        for (String key : retMap.keySet()) {
            retList.addAll(retMap.get(key));
        }
        return retList;
    }

    /**
     * 计算总数
     *
     * @param name     部门/员工名称
     * @param year     年
     * @param month    月
     * @param retMap   返回的map
     * @return {@link List<List<String>>}
     */
    private List<List<String>> calcTotal(String name, Integer year, Integer month, Map<String, List<List<String>>> retMap) {
        List<List<String>> deptTotal = Lists.newArrayList();
        int deptTotalSupply = 0;
        for (String s : retMap.keySet()) {
            List<List<String>> lists = retMap.get(s);
            for (List<String> row : lists) {
                if (! "".equals(row.get(3))) {
                    deptTotalSupply += Integer.parseInt(row.get(3));
                }
            }
        }
        DecimalFormat df = new DecimalFormat("0.00");
        deptTotal.add(Arrays.asList(name, year + "年" + month + "月份", "餐补总计：", df.format(deptTotalSupply * 1.0 / 2), ""));
        return deptTotal;
    }


    /**
     * 查询某个员工某年某月的加班餐补明细
     *
     * @param empName  员工名称
     * @param empId    员工ID(钉钉userId)
     * @param year     年
     * @param month    月
     * @return 返回该员工的加班补贴明细
     */
    public Map<String, List<List<String>>> getEmpOverTimeAttendList(String empName, String empId, Integer year, Integer month) throws Exception {
        List<DingUserVO> dingUserList = Lists.newArrayList();
        dingUserList.add(DingUserVO.builder().userid(empId).name(empName).build());

        String beginDateStr = MyDateUtil.getBeginDateStr(year, month);
        String endDateStr = MyDateUtil.getEndDateStr(year, month);
        List<String> dateList = MyDateUtil.getDateList(5, beginDateStr + "~" + endDateStr, 180);

        Map<String, List<List<String>>> retMap = this.getEmpDayRecords(year, month, dateList, dingUserList);
        Map<String, List<List<String>>> finalMap = Maps.newLinkedHashMap();
        if (dingUserList.size() > 1) {
            List<List<String>> empTotal = this.calcTotal(empName, year, month, retMap);
            finalMap.put(empName + year + "年" + month + "月份-餐补总计", empTotal);
        }
        finalMap.putAll(retMap);
        return finalMap;
    }

    /**
     * 获取员工每日餐补详情及总餐补数据
     *
     * @param year         年
     * @param month        月
     * @param dateList     日期列表
     * @param dingUserList 钉钉用户列表
     * @return {@link Map<String, List<List<String>>>}
     * @throws Exception 异常
     */
    private Map<String, List<List<String>>> getEmpDayRecords(Integer year, Integer month, List<String> dateList, List<DingUserVO> dingUserList) throws Exception {
        Map<String, List<List<String>>> retMap = Maps.newHashMap();
        for (DingUserVO vo : dingUserList) {
            List<List<String>> empRows = Lists.newArrayList();
            String userId = vo.getUserid();
            String name = vo.getName();
            // 每个员工的总计餐补
            int totalSupply = 0;
            for (String date : dateList) {
                List<String> row = Lists.newArrayList();
                row.add(name);
                row.add(date);
                // 查询员工指定日期的打卡记录
                if (ChineseCalendar.NON_WORK_DAY.contains(date)) {
//                    row.add("节假日");
//                    row.add("餐补");
//                    row.add("0");
//                    empRows.add(row);
                    continue;
                }
                TokenFrm tokenFrm = TokenFrm.builder().access_token(getDingToken()).build();
                AttendListFrm frm = AttendListFrm.builder().workDateFrom(date + " 00:00:00").workDateTo(date + " 23:59:59")
                        .userIdList(Collections.singletonList(userId)).offset(0).limit(50).build();
                DingAttendListVO result = dingTalkFeign.getAttendList(tokenFrm, frm);
                try {
                    if (!Objects.equals(result.getErrcode(), DingTalkResource.DD_OK)) {
                        log.info("调用钉钉获取部门下用户的接口返回错误，提示信息：{}", result.getErrmsg());
                        continue;
                    }
                    List<DingRecordVO> list = result.getRecordresult();
                    totalSupply += getDaySupply(empRows, row, date, list);
                } catch (Exception e) {
                    log.error("调用钉钉获取考勤接口异常：", e);
                }
            }
            empRows.add(Arrays.asList(name, year + "年" + month + "月份", "餐补总计：", totalSupply + "", ""));
            // 加一行空行
            empRows.add(Arrays.asList("", "", "", "", ""));
            retMap.put(name, empRows);
        }
        return retMap;
    }

    /**
     * 计算每日餐补并组装excel所需的集合
     *
     * @param empRows 员工所有的餐补记录行
     * @param row     行
     * @param date    日期
     * @param list    列表
     * @return int    总计餐补
     * @throws Exception 异常
     */
    private int getDaySupply(List<List<String>> empRows, List<String> row, String date, List<DingRecordVO> list) throws Exception {
        Date overTime1 = MyDateUtil.parseDate(date + " 20:00:00", MyDateUtil.NORMAL_PATTERN_2);
        Date overTime2 = MyDateUtil.parseDate(date + " 20:30:00", MyDateUtil.NORMAL_PATTERN_2);
        String offDutyTime;
        int mealSupply = 0;

        if (!CollectionUtils.isEmpty(list)) {
            int flag = 0;
            for (DingRecordVO record : list) {
                // 找到下班的那次打卡(最多1次)
                if (Objects.equals(record.getCheckType(), "OffDuty") && Objects.equals(record.getLocationResult(), "Normal")) {
                    flag++;
                    Date userCheckTime = new Date(record.getUserCheckTime().getTime());
                    offDutyTime = MyDateUtil.format(userCheckTime, MyDateUtil.SHORT_PATTERN_5);

                    if (overTime1.getTime() <= userCheckTime.getTime() && userCheckTime.getTime() < overTime2.getTime()) {
                        mealSupply = 20;
                        row.add(offDutyTime);
                        row.add(mealSupply + "");
                        row.add("餐补");
                        empRows.add(row);
                    } else if (userCheckTime.getTime() >= overTime2.getTime()) {
                        mealSupply = 25;
                        row.add(offDutyTime);
                        row.add(mealSupply + "");
                        row.add("餐补");
                        empRows.add(row);
                    } else {
//                        row.add(offDutyTime);
//                        row.add("餐补");
//                        row.add(mealSupply + "");
//                        empRows.add(row);
                    }
                    break;
                }
            }
//            if (flag == 0) {
//                row.add("下班未打卡");
//                row.add("餐补");
//                row.add(mealSupply + "");
//                empRows.add(row);
//            }
        } else {
//            row.add("无打卡记录");
//            row.add("餐补");
//            row.add(mealSupply + "");
//            empRows.add(row);
        }
        return mealSupply;
    }

    /**
     * 查询当前是否存在同步任务
     *
     * @return {@link Boolean}
     */
    public Boolean getSyncStatus() {
        return redisService.hasKey(SysModuleConstant.SYNC_DEPT_USER_LOCK);
    }

    /**
     * 同步钉钉部门和员工信息
     *
     * @return {@link Boolean}
     */
    public Boolean syncDingTalkDeptAndEmployee() {
        // 如果当前已有同步任务在跑，则直接返回失败
        if (getSyncStatus()) {
            return false;
        }
        RLock lock = redisson.getLock(SysModuleConstant.SYNC_DEPT_USER_LOCK);
        try {
            // 给3分钟的同步时间
            boolean locked = lock.tryLock(180, TimeUnit.SECONDS);
            if (locked) {
                CompletableFuture<Boolean> f1 = CompletableFuture.supplyAsync(this::syncDingTalkDept);
                CompletableFuture<Boolean> f2 = CompletableFuture.supplyAsync(this::syncDingTalkEmployee);
                CompletableFuture.supplyAsync(() -> cacheDeptTree(1L));
                CompletableFuture.allOf(f1, f2).join();
            }
        } catch (Exception e) {
            log.error("获取分布式锁失败，或同步出现异常：", e);
        } finally {
            lock.unlock();
        }
        return true;
    }

    /**
     * 异步钉钉部门
     *
     * @return {@link Boolean}
     */
    public Boolean syncDingTalkDept() {
        List<DingDeptVO> dingDeptList = this.getDingDeptList();
        if (CollectionUtils.isEmpty(dingDeptList)) {
            return true;
        }
        dingDeptList.forEach(dept -> {
            Long dingDeptId = dept.getId();
            Long parentId = dept.getParentid();
            String deptName = dept.getName();
            // 先查询，存在则更新，不存在则新增
            SysDept exists = sysDeptDao.selectByPrimaryKey(dingDeptId);
            if (exists != null) {
                exists.setName(deptName);
                exists.setParentId(parentId);
                exists.setUpdateTime(new Date());
                sysDeptDao.updateByPrimaryKeySelective(exists);
            } else {
                SysDept sysDept = SysDept.builder().id(dingDeptId).parentId(parentId).name(deptName).enabled(1).createTime(new Date()).build();
                sysDeptDao.insert(sysDept);
            }
        });
        // 删除掉钉钉中已删除的部门
        List<Long> dingDeptIds = dingDeptList.stream().map(DingDeptVO::getId).collect(Collectors.toList());
        List<Long> sysDeptIds = sysDeptDao.findAllDeptIds();
        if (!CollectionUtils.isEmpty(sysDeptIds)) {
            List<Long> deletedIds = sysDeptIds.stream().filter(id -> !dingDeptIds.contains(id)).collect(Collectors.toList());
            if (!CollectionUtils.isEmpty(deletedIds)) {
                sysDeptDao.batchDeleteDepts(deletedIds);
                deptUserDao.batchDeleteDeptRelation(deletedIds);
            }
        }
        return true;
    }

    /**
     * 同步钉钉员工
     *
     * @return {@link Boolean}
     */
    public Boolean syncDingTalkEmployee() {
        long rootDeptId = 1;
        try {
            DingUserListVO result = dingTalkFeign.getDeptUserList(GetDeptUserFrm.builder().access_token(getDingToken()).department_id(rootDeptId).build());
            if (Objects.equals(result.getErrcode(), DingTalkResource.DD_OK)) {
                List<DingUserVO> list = result.getUserlist();
                if (!CollectionUtils.isEmpty(list)) {
                    list.forEach(dingUserVO -> saveDeptUser(rootDeptId, dingUserVO));
                }

                List<Long> subIdList = this.getDingSubDeptIdList(rootDeptId, true);
                if (!CollectionUtils.isEmpty(subIdList)) {
                    for (Long subId : subIdList) {
                        List<DingUserVO> dingUserList = getDingUserList(subId, false);
                        if (CollectionUtils.isEmpty(dingUserList)) {
                            continue;
                        }
                        dingUserList.forEach(user -> saveDeptUser(subId, user));
                    }
                }
            } else {
                log.info("调用钉钉获取部门下用户的接口返回错误，提示信息：{}", result.getErrmsg());
            }
        } catch (Exception e) {
            log.error("调用钉钉获取部门下用户的接口异常：", e);
        }
        return true;
    }

    private void saveDeptUser(long rootDeptId, DingUserVO dingUserVO) {
        String dtUserId = dingUserVO.getUserid();
        String name = dingUserVO.getName();
        String dingToken = getDingToken();
        try {
            DingUserDetailVO dingUserDetailVO = dingTalkFeign.getUserDetail(GetUserDetailFrm.builder().access_token(dingToken).userid(dtUserId).build());
            int detailCode = dingUserDetailVO.getErrcode();
            if (!Objects.equals(detailCode, DingTalkResource.DD_OK)) {
                log.info("钉钉查询用户详情接口返回异常，请求详情:{} 错误码={}，提示信息={}", detailCode, dingUserDetailVO.getErrmsg());
                return;
            }
            // log.info("钉钉用户信息：{}", JSON.toJSONString(dingUserDetailVO));
            // 先根据钉钉ID查询用户是否已存在，存在则更新，不存在则新增
            SysUser exists = sysUserDao.findByDtUserId(dtUserId);
            BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
            String mobile = dingUserDetailVO.getMobile();
            String email = dingUserDetailVO.getEmail();
            // 默认使用钉钉userId作为用户名
            String username = dingUserDetailVO.getUserid();
            String tel = StringUtils.isEmpty(dingUserDetailVO.getTel()) ? "" : dingUserDetailVO.getTel();
            String avatar = StringUtils.isEmpty(dingUserDetailVO.getAvatar()) ? SysModuleConstant.DEFAULT_USER_AVATAR : dingUserDetailVO.getAvatar();
            String remark = dingUserDetailVO.getRemark();
            if (exists == null) {
                if (!StringUtils.isEmpty(mobile)) {
                    // 优先使用手机号作为用户名
                    username = mobile;
                } else if (!StringUtils.isEmpty(email)) {
                    // 其次使用钉钉邮箱作为用户名
                    username = email;
                }
                SysUser sysUser = SysUser.builder().dtUserId(dtUserId).name(name)
                        .username(username).password(encoder.encode(SysModuleConstant.DEFAULT_PWD))
                        .phone(mobile).telephone(tel).address(dingUserDetailVO.getWorkPlace())
                        .avatar(avatar).remark(remark)
                        // 设置不能登录系统，要登录系统需要管理员授权
                        .enabled(false)
                        .createTime(new Date())
                        .build();
                // 新增用户
                sysUserDao.insertSelective(sysUser);
                // 新增部门-用户关联关系
                deptUserDao.insertSelective(DeptUser.builder().deptId(rootDeptId).userId(sysUser.getId()).build());
            } else {
                exists.setName(name);
                exists.setUsername(username);
                exists.setPhone(mobile);
                exists.setTelephone(tel);
                exists.setAddress(dingUserDetailVO.getWorkPlace());
                exists.setRemark(remark);
                exists.setUpdateTime(new Date());
                // 更新用户
                sysUserDao.updateByPrimaryKeySelective(exists);
                // 更新部门-用户关联关系
                deptUserDao.updateDeptUserRelation(exists.getId(), rootDeptId);
            }
        } catch (Exception e) {
            log.error("调用钉钉查询用户详情接口失败：", e);
        }
    }


    /**
     * 获取部门树
     *
     * @param deptId 部门ID
     * @return {@link List<DeptTreeVO>}
     */
    public List<DeptTreeVO> getDeptTree(Long deptId) {
        List<DeptTreeVO> retList = Lists.newArrayList();
        if (deptId == null) {
            deptId = 1L;
        }
        DeptTreeVO vo;
        while (true) {
            vo = getDingDeptDetail(deptId, null);
            if (vo != null || vo.getBreakFlag()) {
                break;
            }
        }
        retList.add(vo);
        return retList;
    }

    public DeptTreeVO getDingDeptDetail(Long deptId, DeptTreeVO vo) {
        try {
            String dingToken = getDingToken();
            DingDeptDetailVO deptDetail = dingTalkFeign.getDeptDetail(GetDeptDetailFrm.builder().access_token(dingToken).id(deptId.toString()).build());
            if (deptDetail == null || !Objects.equals(deptDetail.getErrcode(), DingTalkResource.DD_OK)) {
                log.info("调用钉钉接口返回非正常，错误码：{} 错误提示信息：{}", deptDetail.getErrcode(), deptDetail.getErrmsg());
                return null;
            }
            vo = DeptTreeVO.builder().id(deptDetail.getId()).label(deptDetail.getName()).build();
            // 查询该部门的子部门
            SubDeptIdListVO resultVO = dingTalkFeign.getDingSubDeptIdList(TokenFrm.builder().access_token(dingToken).build(), GetSubDeptIdFrm.builder().dept_id(deptId).build());
            if (Objects.equals(resultVO.getErrcode(), DingTalkResource.DD_OK)) {
                List<Long> result = resultVO.getResult().getDept_id_list();
                if (result == null || result.isEmpty()) {
                    vo.setChildren(Lists.newArrayList());
                    return vo;
                }
                List<DeptTreeVO> subDeptList = Lists.newArrayList();
                for (Long subId : result) {
                    // 根据子部门再去查询部门信息
                    DingDeptDetailVO subDeptDetail = dingTalkFeign.getDeptDetail(GetDeptDetailFrm.builder().access_token(dingToken).id(subId.toString()).build());
                    if (subDeptDetail == null || !Objects.equals(subDeptDetail.getErrcode(), DingTalkResource.DD_OK)) {
                        log.info("子部门ID={}从钉钉部门详情接口查不到数据", subId);
                        continue;
                    }
                    DeptTreeVO subDeptVO = DeptTreeVO.builder().id(subDeptDetail.getId()).label(subDeptDetail.getName()).build();
                    subDeptList.add(getDingDeptDetail(subId, subDeptVO));
                }
                vo.setChildren(subDeptList);
            } else {
                log.info("调用钉钉获取部门子部门ID集合的接口返回错误，提示信息：{}", resultVO.getErrmsg());
            }
        } catch (Exception e) {
            log.error("调用钉钉获取部门子部门ID集合的接口异常：", e);
        }
        return vo;
    }


    /**
     * 缓存部门树
     *
     * @param deptId 部门id
     * @return {@link Boolean}
     */
    public Boolean cacheDeptTree(Long deptId) {
        if (deptId == null) {
            deptId = 1L;
        }
        List<DeptTreeVO> deptTree = this.getDeptTree(deptId);
        if (CollectionUtils.isEmpty(deptTree)) {
            deptTree = Lists.newArrayList();
        }
        return redisService.set(SysModuleConstant.DEPT_TREE_CACHE_KEY + deptId, JSON.toJSONString(deptTree));
    }

    /**
     * 获取缓存的部门树
     *
     * @param deptId 部门id
     * @return {@link List<DeptTreeVO>}
     */
    public List<DeptTreeVO> getCachedDeptTree(Long deptId) {
        if (deptId == null) {
            deptId = 1L;
        }
        String deptTree = (String) redisService.get(SysModuleConstant.DEPT_TREE_CACHE_KEY + deptId);
        return JSON.parseObject(deptTree, List.class);
    }

    /**
     * 查询钉钉部门成员ID集合
     *
     * @param deptId 部门id
     * @return {@link List<String>}
     */
    public List<String> getDeptMemberIdList(Long deptId) {
        List<String> retList = Lists.newArrayList();
        deptId = deptId == null ? 1L : deptId;
        String dingToken = getDingToken();
        DeptUserIdListFrm frm = DeptUserIdListFrm.builder().dept_id(deptId).build();
        try {
            ListUserByDeptIdResultVO result = dingTalkFeign.getDeptUserIdList(TokenFrm.builder().access_token(dingToken).build(), frm);
            if (result == null || !Objects.equals(result.getErrcode(), DingTalkResource.DD_OK)) {
                log.error("调用钉钉获取部门(id={})用户ID集合接口返回失败，提示信息：{} request_id:{}", deptId, result.getErrmsg(), result.getRequest_id());
                return retList;
            }
            retList = result.getResult().getUserid_list();
        } catch (Exception e) {
            log.error("调用钉钉获取部门(id={})用户ID集合接口异常：", deptId, e);
        }
        return retList;
    }

    /**
     * 查询钉钉用户详情
     *
     * @param frm 查询参数
     * @return {@link DingUserDetailVO}
     */
    public DingUserDetailVO getDingUserDetail(GetUserDetailFrm frm) {
        frm.setAccess_token(getDingToken());
        return dingTalkFeign.getUserDetail(frm);
    }

}
